// Copyright (c) 2020 Samsung Electronics Co., LTD
// Distributed under the MIT License.
// See the LICENSE file in the project root for more information.

#ifdef _MSC_VER
#include <wtypes.h>
#endif

#include "debugger/managedcallback.h"
#include "debugger/threads.h"
#include "debugger/evalwaiter.h"
#include "debugger/breakpoint_break.h"
#include "debugger/breakpoint_entry.h"
#include "debugger/breakpoints_exception.h"
#include "debugger/breakpoints_func.h"
#include "debugger/breakpoints_line.h"
#include "debugger/breakpoints.h"
#include "debugger/valueprint.h"
#include "debugger/waitpid.h"
#include "debugger/stepper_simple.h"
#include "debugger/stepper_async.h"
#include "debugger/steppers.h"
#include "metadata/modules.h"
#include "metadata/typeprinter.h"
#include "interfaces/iprotocol.h"
#include "utils/utf.h"

namespace netcoredbg
{

bool ManagedCallback::CallbacksWorkerBreakpoint(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)
{
    HRESULT Status;
    if (SUCCEEDED(Status = m_debugger.m_uniqueSteppers->ManagedCallbackBreakpoint(pAppDomain, pThread)) &&
        Status == S_OK) // S_FALSE - no error, but steppers not affect on callback
    {
        return false;
    }

    bool atEntry = false;
    ThreadId threadId(getThreadId(pThread));
    StoppedEvent event(StopBreakpoint, threadId);
    if (SUCCEEDED(Status = m_debugger.m_uniqueBreakpoints->ManagedCallbackBreakpoint(&m_debugger, pThread, pBreakpoint, event.breakpoint, atEntry)) &&
        Status == S_OK) // S_FALSE - no error, not affect on callback (callback will emit stop event)
    {
        return false;
    }

    // Disable all steppers if we stop at breakpoint during step.
    m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);

    if (atEntry)
        event.reason = StopEntry;

    ToRelease<ICorDebugFrame> pFrame;
    if (SUCCEEDED(pThread->GetActiveFrame(&pFrame)) && pFrame != nullptr)
        m_debugger.GetFrameLocation(pFrame, threadId, FrameLevel(0), event.frame);

    m_debugger.SetLastStoppedThread(pThread);
    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
    m_debugger.m_ioredirect.async_cancel();
    return true;
}

bool ManagedCallback::CallbacksWorkerStepComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, CorDebugStepReason reason)
{
    HRESULT Status;
    if (SUCCEEDED(Status = m_debugger.m_uniqueSteppers->ManagedCallbackStepComplete(pThread, reason)) &&
        Status == S_OK) // S_FALSE - no error, but steppers not affect on callback
    {
        return false;
    }

    StackFrame stackFrame;
    ToRelease<ICorDebugFrame> iCorFrame;
    ThreadId threadId(getThreadId(pThread));
    if (SUCCEEDED(pThread->GetActiveFrame(&iCorFrame)) && iCorFrame != nullptr)
        m_debugger.GetFrameLocation(iCorFrame, threadId, FrameLevel(0), stackFrame);

    StoppedEvent event(StopStep, threadId);
    event.frame = stackFrame;

    m_debugger.SetLastStoppedThread(pThread);
    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
    m_debugger.m_ioredirect.async_cancel();
    return true;
}

bool ManagedCallback::CallbacksWorkerBreak(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
    HRESULT Status;
    if (SUCCEEDED(Status = m_debugger.m_uniqueBreakpoints->ManagedCallbackBreak(pThread, m_debugger.GetLastStoppedThreadId())) &&
        Status == S_OK) // S_FALSE - no error, not affect on callback (callback will emit stop event)
    {
        return false;
    }

    // Disable all steppers if we stop at break during step.
    m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);

    m_debugger.SetLastStoppedThread(pThread);
    ThreadId threadId(getThreadId(pThread));
    StackFrame stackFrame;

    ToRelease<ICorDebugFrame> iCorFrame;
    if (SUCCEEDED(pThread->GetActiveFrame(&iCorFrame)) && iCorFrame != nullptr)
        m_debugger.GetFrameLocation(iCorFrame, threadId, FrameLevel(0), stackFrame);

    StoppedEvent event(StopPause, threadId);
    event.frame = stackFrame;
    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
    m_debugger.m_ioredirect.async_cancel();
    return true;
}

bool ManagedCallback::CallbacksWorkerException(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ExceptionCallbackType eventType, const std::string &excModule)
{
    ThreadId threadId(getThreadId(pThread));
    StoppedEvent event(StopException, threadId);

    HRESULT Status;
    if (SUCCEEDED(Status = m_debugger.m_uniqueBreakpoints->ManagedCallbackException(pThread, eventType, excModule, event)) &&
        Status == S_OK) // S_FALSE - no error, not affect on callback (callback will emit stop event)
    {
        return false;
    }

    ToRelease<ICorDebugFrame> pActiveFrame;
    if (SUCCEEDED(pThread->GetActiveFrame(&pActiveFrame)) && pActiveFrame != nullptr)
        m_debugger.GetFrameLocation(pActiveFrame, threadId, FrameLevel(0), event.frame);

    // Disable all steppers if we stop during step.
    m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);

    m_debugger.SetLastStoppedThread(pThread);

    m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
    m_debugger.m_ioredirect.async_cancel();
    return true;
}

void ManagedCallback::CallbacksWorker()
{
    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    while (true)
    {
        while (m_callbacksQueue.empty() || m_stopEventInProcess)
        {
            // Note, during m_callbacksCV.wait() (waiting for notify_one call with entry added into queue),
            // m_callbacksMutex will be unlocked (see std::condition_variable for more info).
            m_callbacksCV.wait(lock);
        }

        auto &c = m_callbacksQueue.front();

        switch (c.Call)
        {
        case CallbackQueueCall::Breakpoint:
            m_stopEventInProcess = CallbacksWorkerBreakpoint(c.iCorAppDomain, c.iCorThread, c.iCorBreakpoint);
            break;
        case CallbackQueueCall::StepComplete:
            m_stopEventInProcess = CallbacksWorkerStepComplete(c.iCorAppDomain, c.iCorThread, c.Reason);
            break;
        case CallbackQueueCall::Break:
            m_stopEventInProcess = CallbacksWorkerBreak(c.iCorAppDomain, c.iCorThread);
            break;
        case CallbackQueueCall::Exception:
            m_stopEventInProcess = CallbacksWorkerException(c.iCorAppDomain, c.iCorThread, c.EventType, c.ExcModule);
            break;
        default:
            // finish loop
            // called from destructor only, don't need call pop()
            return;
        }

        ToRelease<ICorDebugAppDomain> iCorAppDomain(c.iCorAppDomain.Detach());
        m_callbacksQueue.pop_front();

        // Continue process execution only in case we don't have stop event emitted and queue is empty.
        // We safe here against fast Continue()/AddCallbackToQueue() call from new callback call, since we don't unlock m_callbacksMutex.
        // m_callbacksMutex will be unlocked only in m_callbacksCV.wait(), when CallbacksWorker will be ready for notify_one.
        if (m_callbacksQueue.empty() && !m_stopEventInProcess)
            iCorAppDomain->Continue(0);
    }
}

bool ManagedCallback::HasQueuedCallbacks(ICorDebugProcess *pProcess)
{
    BOOL bQueued = FALSE;
    pProcess->HasQueuedCallbacks(NULL, &bQueued);
    return bQueued == TRUE;
}

HRESULT ManagedCallback::AddCallbackToQueue(ICorDebugAppDomain *pAppDomain, std::function<void()> callback)
{
    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
    {
        pAppDomain->Continue(0);
        return S_OK;
    }

    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    callback();
    assert(!m_callbacksQueue.empty());

    // Note, we don't check m_callbacksQueue.empty() here, since callback() must add entry to queue.
    ToRelease<ICorDebugProcess> iCorProcess;
    if (SUCCEEDED(pAppDomain->GetProcess(&iCorProcess)) && HasQueuedCallbacks(iCorProcess))
        pAppDomain->Continue(0);
    else
        m_callbacksCV.notify_one(); // notify_one with lock

    return S_OK;
}

HRESULT ManagedCallback::ContinueAppDomainWithCallbacksQueue(ICorDebugAppDomain *pAppDomain)
{
    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
    {
        if (!pAppDomain)
            return E_NOTIMPL;

        pAppDomain->Continue(0);
        return S_OK;
    }

    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    ToRelease<ICorDebugProcess> iCorProcess;
    if (m_callbacksQueue.empty() || (pAppDomain && SUCCEEDED(pAppDomain->GetProcess(&iCorProcess)) && HasQueuedCallbacks(iCorProcess)))
    {
        if (!pAppDomain)
            return E_NOTIMPL;

        pAppDomain->Continue(0);
    }
    else
        m_callbacksCV.notify_one(); // notify_one with lock

    return S_OK;
}

HRESULT ManagedCallback::ContinueProcessWithCallbacksQueue(ICorDebugProcess *pProcess)
{
    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
    {
        if (!pProcess)
            return E_NOTIMPL;

        pProcess->Continue(0);
        return S_OK;
    }

    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    if (m_callbacksQueue.empty() || (pProcess && HasQueuedCallbacks(pProcess)))
    {
        if (!pProcess)
            return E_NOTIMPL;

        pProcess->Continue(0);
    }
    else
        m_callbacksCV.notify_one(); // notify_one with lock

    return S_OK;
}

bool ManagedCallback::IsRunning()
{
    std::unique_lock<std::mutex> lock(m_callbacksMutex);
    return !m_stopEventInProcess;
}

HRESULT ManagedCallback::Continue(ICorDebugProcess *pProcess)
{
    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    assert(m_stopEventInProcess);
    m_stopEventInProcess = false;

    if (m_callbacksQueue.empty())
        return pProcess->Continue(0);

    m_callbacksCV.notify_one(); // notify_one with lock
    return S_OK;
}

HRESULT ManagedCallback::Pause(ICorDebugProcess *pProcess)
{
    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    if (m_stopEventInProcess)
        return S_OK;

    // Note, in case Stop() failed, no stop event will be emitted, don't set m_stopEventInProcess to "true" in this case.
    HRESULT Status;
    if (FAILED(Status = pProcess->Stop(0)))
        return Status;

    m_stopEventInProcess = true;

    // Same logic as provide vsdbg in case of pause during stepping.
    m_debugger.m_uniqueSteppers->DisableAllSteppers(m_debugger.m_iCorProcess);

    // For Visual Studio, we have to report a thread ID in async stop event.
    // We have to find a thread which has a stack frame with valid location in its stack trace.
    std::vector<Thread> threads;
    m_debugger.GetThreads(threads);

    ThreadId lastStoppedId = m_debugger.GetLastStoppedThreadId();

    // Reorder threads so that last stopped thread is checked first
    for (size_t i = 0; i < threads.size(); ++i)
    {
        if (threads[i].id == lastStoppedId)
        {
            std::swap(threads[0], threads[i]);
            break;
        }
    }

    // Now get stack trace for each thread and find a frame with valid source location.
    for (const Thread& thread : threads)
    {
        int totalFrames = 0;
        std::vector<StackFrame> stackFrames;

        if (FAILED(m_debugger.GetStackTrace(thread.id, FrameLevel(0), 0, stackFrames, totalFrames)))
            continue;

        for (const StackFrame& stackFrame : stackFrames)
        {
            if (stackFrame.source.IsNull())
                continue;

            StoppedEvent event(StopPause, thread.id);
            event.frame = stackFrame;
            m_debugger.SetLastStoppedThreadId(thread.id);
            m_debugger.m_sharedProtocol->EmitStoppedEvent(event);
            m_debugger.m_ioredirect.async_cancel();
            return S_OK;
        }
    }

    m_debugger.m_sharedProtocol->EmitStoppedEvent(StoppedEvent(StopPause, ThreadId::Invalid));
    m_debugger.m_ioredirect.async_cancel();
    return S_OK;
}

ManagedCallback::~ManagedCallback()
{
    std::unique_lock<std::mutex> lock(m_callbacksMutex);

    // Clear queue and do notify_one call with FinishWorker request.
    m_callbacksQueue.clear();
    m_callbacksQueue.emplace_front(CallbackQueueCall::FinishWorker, nullptr, nullptr, nullptr, STEP_NORMAL, ExceptionCallbackType::FIRST_CHANCE);
    m_stopEventInProcess = false; // forced to proceed during brake too
    m_callbacksCV.notify_one(); // notify_one with lock
    lock.unlock();
    m_callbacksWorker.join();
}

ULONG ManagedCallback::GetRefCount()
{
    LogFuncEntry();

    std::lock_guard<std::mutex> lock(m_refCountMutex);
    return m_refCount;
}


// IUnknown

HRESULT STDMETHODCALLTYPE ManagedCallback::QueryInterface(REFIID riid, VOID** ppInterface)
{
    LogFuncEntry();

    if (riid == IID_ICorDebugManagedCallback)
    {
        *ppInterface = static_cast<ICorDebugManagedCallback*>(this);
    }
    else if (riid == IID_ICorDebugManagedCallback2)
    {
        *ppInterface = static_cast<ICorDebugManagedCallback2*>(this);
    }
    else if (riid == IID_ICorDebugManagedCallback3)
    {
        *ppInterface = static_cast<ICorDebugManagedCallback3*>(this);
    }
    else if (riid == IID_IUnknown)
    {
        *ppInterface = static_cast<IUnknown*>(static_cast<ICorDebugManagedCallback*>(this));
    }
    else
    {
        *ppInterface = NULL;
        return E_NOINTERFACE;
    }

    this->AddRef();
    return S_OK;
}

ULONG STDMETHODCALLTYPE ManagedCallback::AddRef()
{
    LogFuncEntry();

    std::lock_guard<std::mutex> lock(m_refCountMutex);
    return ++m_refCount;
}

ULONG STDMETHODCALLTYPE ManagedCallback::Release()
{
    LogFuncEntry();

    std::lock_guard<std::mutex> lock(m_refCountMutex);

    assert(m_refCount > 0);

    // Note, we don't provide "delete" call for object itself for our fake "COM".
    // External holder will care about this object during debugger lifetime.

    return --m_refCount;
}


// ICorDebugManagedCallback

HRESULT STDMETHODCALLTYPE ManagedCallback::Breakpoint(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)
{
    LogFuncEntry();
    return AddCallbackToQueue(pAppDomain, [&]()
    {
        pAppDomain->AddRef();
        pThread->AddRef();
        pBreakpoint->AddRef();
        m_callbacksQueue.emplace_back(CallbackQueueCall::Breakpoint, pAppDomain, pThread, pBreakpoint, STEP_NORMAL, ExceptionCallbackType::FIRST_CHANCE);
    });
}

HRESULT STDMETHODCALLTYPE ManagedCallback::StepComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                        ICorDebugStepper *pStepper, CorDebugStepReason reason)
{
    LogFuncEntry();
    return AddCallbackToQueue(pAppDomain, [&]()
    {
        pAppDomain->AddRef();
        pThread->AddRef();
        m_callbacksQueue.emplace_back(CallbackQueueCall::StepComplete, pAppDomain, pThread, nullptr, reason, ExceptionCallbackType::FIRST_CHANCE);
    });
}

HRESULT STDMETHODCALLTYPE ManagedCallback::Break(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
    LogFuncEntry();
    return AddCallbackToQueue(pAppDomain, [&]()
    {
        pAppDomain->AddRef();
        pThread->AddRef();
        m_callbacksQueue.emplace_back(CallbackQueueCall::Break, pAppDomain, pThread, nullptr, STEP_NORMAL, ExceptionCallbackType::FIRST_CHANCE);
    });
}

HRESULT STDMETHODCALLTYPE ManagedCallback::Exception(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, BOOL unhandled)
{
    // Obsolete callback
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::EvalComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugEval *pEval)
{
    LogFuncEntry();
    m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(pThread, pEval);
    return S_OK; // Eval-related routine - no callbacks queue related code here.
}

HRESULT STDMETHODCALLTYPE ManagedCallback::EvalException(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugEval *pEval)
{
    LogFuncEntry();
    m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(pThread, pEval);
    return S_OK; // Eval-related routine - no callbacks queue related code here.
}

HRESULT STDMETHODCALLTYPE ManagedCallback::CreateProcess(ICorDebugProcess *pProcess)
{
    LogFuncEntry();
    m_debugger.NotifyProcessCreated();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ExitProcess(ICorDebugProcess *pProcess)
{
    LogFuncEntry();

    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
        LOGW("The target process exited while evaluating the function.");

    m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(nullptr, nullptr);

    // Linux: exit() and _exit() argument is int (signed int)
    // Windows: ExitProcess() and TerminateProcess() argument is UINT (unsigned int)
    // Windows: GetExitCodeProcess() argument is DWORD (unsigned long)
    // internal CoreCLR variable LatchedExitCode is INT32 (signed int)
    // C# Main() return values is int (signed int) or void (return 0)
    int exitCode = 0;
#ifdef FEATURE_PAL
    exitCode = GetWaitpid().GetExitCode();
#else
    HPROCESS hProcess;
    DWORD dwExitCode = 0;
    if (SUCCEEDED(pProcess->GetHandle(&hProcess)))
    {
        GetExitCodeProcess(hProcess, &dwExitCode);
        exitCode = static_cast<int>(dwExitCode);
    }
#endif // FEATURE_PAL

    m_debugger.m_sharedProtocol->EmitExitedEvent(ExitedEvent(exitCode));
    m_debugger.NotifyProcessExited();
    m_debugger.m_sharedProtocol->EmitTerminatedEvent();
    m_debugger.m_ioredirect.async_cancel();
    return S_OK;
}

HRESULT STDMETHODCALLTYPE ManagedCallback::CreateThread(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
    LogFuncEntry();

    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
        LOGW("Thread was created by user code during evaluation with implicit user code execution.");

    ThreadId threadId(getThreadId(pThread));
    m_debugger.m_sharedThreads->Add(threadId);

    m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadStarted, threadId));
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ExitThread(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
    LogFuncEntry();

    ThreadId threadId(getThreadId(pThread));
    m_debugger.m_sharedThreads->Remove(threadId);

    m_debugger.m_sharedEvalWaiter->NotifyEvalComplete(pThread, nullptr);
    if (m_debugger.GetLastStoppedThreadId() == threadId)
        m_debugger.InvalidateLastStoppedThreadId();

    m_debugger.m_uniqueBreakpoints->ManagedCallbackExitThread(pThread);

    m_debugger.m_sharedProtocol->EmitThreadEvent(ThreadEvent(ThreadExited, threadId));
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::LoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)
{
    LogFuncEntry();

    Module module;
    m_debugger.m_sharedModules->TryLoadModuleSymbols(pModule, module, m_debugger.IsJustMyCode());
    m_debugger.m_sharedProtocol->EmitModuleEvent(ModuleEvent(ModuleNew, module));

    if (module.symbolStatus == SymbolsLoaded)
    {
        std::vector<BreakpointEvent> events;
        m_debugger.m_uniqueBreakpoints->ManagedCallbackLoadModule(pModule, events);
        for (const BreakpointEvent &event : events)
            m_debugger.m_sharedProtocol->EmitBreakpointEvent(event);
    }

    // enable Debugger.NotifyOfCrossThreadDependency after System.Private.CoreLib.dll loaded (trigger for 1 time call only)
    if (module.name == "System.Private.CoreLib.dll")
        m_debugger.SetEnableCustomNotification(TRUE);

    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::UnloadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::LoadClass(ICorDebugAppDomain *pAppDomain, ICorDebugClass *c)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::UnloadClass(ICorDebugAppDomain *pAppDomain, ICorDebugClass *c)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::DebuggerError(ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::LogMessage(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                      LONG lLevel, WCHAR *pLogSwitchName, WCHAR *pMessage)
{
    LogFuncEntry();

    if (m_debugger.m_sharedEvalWaiter->IsEvalRunning())
    {
        pAppDomain->Continue(0); // Eval-related routine - ignore callbacks queue, continue process execution.
        return S_OK;
    }

    string_view src;
    std::string src_holder;
    if (pLogSwitchName && *pLogSwitchName)
    {
        src_holder = to_utf8(pLogSwitchName);
        src = src_holder;
    }
    else
    {
        src = "Debugger.Log";
    }

    m_debugger.m_sharedProtocol->EmitOutputEvent(OutputConsole, to_utf8(pMessage), src);
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::LogSwitch(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, LONG lLevel,
                                                     ULONG ulReason, WCHAR *pLogSwitchName, WCHAR *pParentName)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::CreateAppDomain(ICorDebugProcess *pProcess, ICorDebugAppDomain *pAppDomain)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ExitAppDomain(ICorDebugProcess *pProcess, ICorDebugAppDomain *pAppDomain)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::LoadAssembly(ICorDebugAppDomain *pAppDomain, ICorDebugAssembly *pAssembly)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::UnloadAssembly(ICorDebugAppDomain *pAppDomain, ICorDebugAssembly *pAssembly)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ControlCTrap(ICorDebugProcess *pProcess)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::NameChange(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::UpdateModuleSymbols(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule, IStream *pSymbolStream)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::EditAndContinueRemap(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                                ICorDebugFunction *pFunction, BOOL fAccurate)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::BreakpointSetError(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                              ICorDebugBreakpoint *pBreakpoint, DWORD dwError)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}


// ICorDebugManagedCallback2

HRESULT STDMETHODCALLTYPE ManagedCallback::FunctionRemapOpportunity(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                                    ICorDebugFunction *pOldFunction, ICorDebugFunction *pNewFunction, ULONG32 oldILOffset)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::CreateConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId, WCHAR *pConnName)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ChangeConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::DestroyConnection(ICorDebugProcess *pProcess, CONNID dwConnectionId)
{
    LogFuncEntry();
    return ContinueProcessWithCallbacksQueue(pProcess);
}

static HRESULT GetExceptionModuleName(ICorDebugFrame *pFrame, std::string &excModule)
{
    HRESULT Status;
    ToRelease<ICorDebugFunction> pFunc;
    IfFailRet(pFrame->GetFunction(&pFunc));

    ToRelease<ICorDebugModule> pModule;
    IfFailRet(pFunc->GetModule(&pModule));

    ToRelease<IUnknown> pMDUnknown;
    ToRelease<IMetaDataImport> pMDImport;
    IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
    IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMDImport));

    WCHAR mdName[mdNameLen];
    ULONG nameLen;
    IfFailRet(pMDImport->GetScopeProps(mdName, _countof(mdName), &nameLen, nullptr));
    excModule = to_utf8(mdName);

    return S_OK;
}

static ExceptionCallbackType CorrectedByJMCCatchHandlerEventType(ICorDebugFrame *pFrame, bool justMyCode)
{
    if (!justMyCode)
        return ExceptionCallbackType::CATCH_HANDLER_FOUND;

    BOOL JMCStatus = FALSE;
    ToRelease<ICorDebugFunction> iCorFunction;
    ToRelease<ICorDebugFunction2> iCorFunction2;
    if (pFrame != nullptr &&
        SUCCEEDED(pFrame->GetFunction(&iCorFunction)) &&
        SUCCEEDED(iCorFunction->QueryInterface(IID_ICorDebugFunction2, (LPVOID*) &iCorFunction2)) &&
        SUCCEEDED(iCorFunction2->GetJMCStatus(&JMCStatus)) &&
        JMCStatus == TRUE)
    {
        return ExceptionCallbackType::USER_CATCH_HANDLER_FOUND;
    }

    return ExceptionCallbackType::CATCH_HANDLER_FOUND;
}

HRESULT STDMETHODCALLTYPE ManagedCallback::Exception(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugFrame *pFrame,
                                                     ULONG32 nOffset, CorDebugExceptionCallbackType dwEventType, DWORD dwFlags)
{
    LogFuncEntry();
    return AddCallbackToQueue(pAppDomain, [&]()
    {
        // pFrame could be neutered in case of evaluation during brake, do all stuff with pFrame in callback itself.
        ExceptionCallbackType eventType;
        std::string excModule;
        switch(dwEventType)
        {
        case DEBUG_EXCEPTION_FIRST_CHANCE:
            eventType = ExceptionCallbackType::FIRST_CHANCE;
            GetExceptionModuleName(pFrame, excModule);
            break;
        case DEBUG_EXCEPTION_USER_FIRST_CHANCE:
            eventType = ExceptionCallbackType::USER_FIRST_CHANCE;
            GetExceptionModuleName(pFrame, excModule);
            break;
        case DEBUG_EXCEPTION_CATCH_HANDLER_FOUND:
            eventType = CorrectedByJMCCatchHandlerEventType(pFrame, m_debugger.IsJustMyCode());
            break;
        default:
            eventType = ExceptionCallbackType::UNHANDLED;
            break;
        }

        pAppDomain->AddRef();
        pThread->AddRef();
        m_callbacksQueue.emplace_back(CallbackQueueCall::Exception, pAppDomain, pThread, nullptr, STEP_NORMAL, eventType, excModule);
    });
}

HRESULT STDMETHODCALLTYPE ManagedCallback::ExceptionUnwind(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread,
                                                           CorDebugExceptionUnwindCallbackType dwEventType, DWORD dwFlags)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::FunctionRemapComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugFunction *pFunction)
{
    LogFuncEntry();
    return ContinueAppDomainWithCallbacksQueue(pAppDomain);
}

HRESULT STDMETHODCALLTYPE ManagedCallback::MDANotification(ICorDebugController *pController, ICorDebugThread *pThread, ICorDebugMDA *pMDA)
{
    LogFuncEntry();
    ToRelease<ICorDebugProcess> iCorProcess;
    pThread->GetProcess(&iCorProcess);
    return ContinueProcessWithCallbacksQueue(iCorProcess);
}


// ICorDebugManagedCallback3

HRESULT STDMETHODCALLTYPE ManagedCallback::CustomNotification(ICorDebugThread *pThread, ICorDebugAppDomain *pAppDomain)
{
    LogFuncEntry();
    m_debugger.m_sharedEvalWaiter->ManagedCallbackCustomNotification(pThread);
    pAppDomain->Continue(0); // Eval-related routine - ignore callbacks queue, continue process execution.
    return S_OK;
}

} // namespace netcoredbg
